#include <raylib.h>

#include <cmath>
#include <cstdlib>
#include <ctime>
#include <iostream>
#include <map>
#include <memory>
#include <utility>
#include <vector>

using namespace std;

struct Postion {
  Postion(int row_ = 0, int col_ = 0) {
    row = row_;
    col = col_;
  }
  int row;
  int col;
};
enum class DIRECTION {
  MOVE_LEFT = 0,
  MOVE_RIGHT = 1,
  MOVE_UP = 2,
  MOVE_DOWN = 3
};
class AStarSearch {
  AStarSearch(int** ptr_cells, int cell_num) {
    for (size_t i = 0; i < cell_num; i++) {
      vector<int> tmp;
      for (size_t j = 0; j < cell_num; j++) {
        tmp.push_back(ptr_cells[i][j]);
        if (ptr_cells[i][j] == 0) {
          zero_pos = {i, j};
        }
      }

      cells.emplace_back(tmp);
    }
  }

  void run() {
    while (!finish()) {
      auto direct = search();
      std::cout << "move: " << (int)direct << std::endl;
    }
  }

  inline bool finish() {
    int cell_num = cells.size();
    for (size_t i = 0; i < cell_num; i++) {
      for (size_t j = 0; j < cell_num; j++) {
        if (cells[i][j] != i * cell_num + j + 1) {
          return false;
        }
      }
    }
    return true;
  }

 private:
  static int distanch(int** cells, int cell_num, Postion zero) {
    for (int i = 0; i < cell_num; i++) {
      for (int j = 0; j < cell_num; j++) {
        int real_row = cells[i][j] / cell_num;
        int real_col = cells[i][j] % cell_num;
        return (i - real_row) * (i - real_row) +
               (j - real_col) * (j - real_col);
      }
    }
  }
  DIRECTION search() {
    // TODO
    return DIRECTION::MOVE_LEFT;
  }
  void move(DIRECTION direct) {
    switch (direct) {
      case DIRECTION::MOVE_LEFT:
        cells[zero_pos.row][zero_pos.col] =
            cells[zero_pos.row][zero_pos.col + 1];
        cells[zero_pos.row][zero_pos.col + 1] = 0;
        zero_pos.col += 1;
        break;
      case DIRECTION::MOVE_RIGHT:
        cells[zero_pos.row][zero_pos.col] =
            cells[zero_pos.row][zero_pos.col - 1];
        cells[zero_pos.row][zero_pos.col - 1] = 0;
        zero_pos.col -= 1;

        break;
      case DIRECTION::MOVE_UP:
        cells[zero_pos.row][zero_pos.col] =
            cells[zero_pos.row + 1][zero_pos.col];
        cells[zero_pos.row + 1][zero_pos.col] = 0;
        zero_pos.row += 1;
        break;
      case DIRECTION::MOVE_DOWN:
        cells[zero_pos.row][zero_pos.col] =
            cells[zero_pos.row - 1][zero_pos.col];
        cells[zero_pos.row - 1][zero_pos.col] = 0;
        zero_pos.row -= 1;
        break;
      default:
        break;
    }
  }

 private:
  vector<vector<int>> cells;
  Postion zero_pos;
};

class NPuzzleGame {
 public:
  NPuzzleGame(int level = 0) {
    cell_num = level + 3;  // 3x3, 4x4, 5x5

    resetGame();

    InitWindow(cell_num * CELL_WIDTH, cell_num * CELL_WIDTH, "Mine Sweep");
    SetTargetFPS(60);
  }
  ~NPuzzleGame() { CloseWindow(); }

  void run() {
    // Main game loop
    while (!WindowShouldClose()) {  // Detect window close button or ESC key

      process_mouse_event();
      process_keyboard_event();

      // Draw
      BeginDrawing();
      drawFrame();
      EndDrawing();
    }
  }

 private:
  // 处理鼠标事件
  void process_mouse_event() {
    Vector2 mouse = GetMousePosition();
    // 鼠标左键点击
    //----------------------------------------------------------------------------------
    if (IsMouseButtonPressed(MOUSE_LEFT_BUTTON)) {
      int col = mouse.x / CELL_WIDTH;
      int row = mouse.y / CELL_WIDTH;
      if (col == zero_pos.col) {
        if (row == zero_pos.row - 1)
          moveCell(DIRECTION::MOVE_DOWN);
        else if (row == zero_pos.row + 1)
          moveCell(DIRECTION::MOVE_UP);

      } else if (row == zero_pos.row) {
        if (col == zero_pos.col - 1)
          moveCell(DIRECTION::MOVE_RIGHT);
        else if (col == zero_pos.col + 1)
          moveCell(DIRECTION::MOVE_LEFT);
      }
    }
  }
  // 处理键盘事件
  void process_keyboard_event() {
    if (IsKeyPressed(KEY_LEFT)) {
      moveCell(DIRECTION::MOVE_LEFT);
    } else if (IsKeyPressed(KEY_RIGHT)) {
      moveCell(DIRECTION::MOVE_RIGHT);
    } else if (IsKeyPressed(KEY_UP)) {
      moveCell(DIRECTION::MOVE_UP);
    } else if (IsKeyPressed(KEY_DOWN)) {
      moveCell(DIRECTION::MOVE_DOWN);
    } else if (IsKeyPressed(KEY_THREE)) {
      cell_num = 3;
      resetGame();
      SetWindowSize(cell_num * CELL_WIDTH, cell_num * CELL_WIDTH);
    } else if (IsKeyPressed(KEY_FOUR)) {
      cell_num = 4;
      resetGame();
      SetWindowSize(cell_num * CELL_WIDTH, cell_num * CELL_WIDTH);
    } else if (IsKeyPressed(KEY_FIVE)) {
      cell_num = 5;
      resetGame();
      SetWindowSize(cell_num * CELL_WIDTH, cell_num * CELL_WIDTH);
    } else if (IsKeyPressed(KEY_S)) {
      AStarSearch search;
    }
  }
  void drawFrame() {
    // Draw the blocks
    for (int i = 0; i < cell_num; ++i) {
      for (int j = 0; j < cell_num; ++j) {
        if (cells[i][j] == 0) {
          DrawRectangle(j * 150, i * 150, 150, 150, DARKGRAY);
          DrawRectangleLines(j * 150, i * 150, 150, 150, BLACK);
        } else {
          int x = cells[i][j] >= 10 ? (j * 150 + 15) : (j * 150 + 50);
          int y = i * 150 + 20;
          DrawRectangle(j * 150, i * 150, 150, 150, LIGHTGRAY);
          DrawRectangleLines(j * 150, i * 150, 150, 150, BLACK);
          DrawText(TextFormat("%i", cells[i][j]), x, y, 120, GREEN);
        }
      }
    }

    // Check for gameover
    if (finished) {
      DrawText("Great!!", 103, 203, 30, BLACK);
      DrawText("Great!!", 100, 200, 30, RED);
    }
  }

  // Restart the game
  void resetGame() {
    srand(time(NULL));

    for (int i = 0; i < cell_num * cell_num - 1; i++) {
      cells[i / cell_num][i % cell_num] = i + 1;
    }
    cells[cell_num - 1][cell_num - 1] = 0;
    zero_pos = {cell_num - 1, cell_num - 1};

    randomMove(5);  // TODO
  }

  /**
   *
   **/
  inline void moveCell(DIRECTION direct) {
    switch (direct) {
      case DIRECTION::MOVE_LEFT:
        if (zero_pos.col != cell_num - 1) {
          cells[zero_pos.row][zero_pos.col] =
              cells[zero_pos.row][zero_pos.col + 1];
          cells[zero_pos.row][zero_pos.col + 1] = 0;
          zero_pos.col += 1;
        }

        break;
      case DIRECTION::MOVE_RIGHT:
        if (zero_pos.col != 0) {
          cells[zero_pos.row][zero_pos.col] =
              cells[zero_pos.row][zero_pos.col - 1];
          cells[zero_pos.row][zero_pos.col - 1] = 0;
          zero_pos.col -= 1;
        }

        break;
      case DIRECTION::MOVE_UP:
        if (zero_pos.row != cell_num - 1) {
          cells[zero_pos.row][zero_pos.col] =
              cells[zero_pos.row + 1][zero_pos.col];
          cells[zero_pos.row + 1][zero_pos.col] = 0;
          zero_pos.row += 1;
        }
        break;
      case DIRECTION::MOVE_DOWN:
        if (zero_pos.row != 0) {
          cells[zero_pos.row][zero_pos.col] =
              cells[zero_pos.row - 1][zero_pos.col];
          cells[zero_pos.row - 1][zero_pos.col] = 0;
          zero_pos.row -= 1;
        }
        break;
      default:
        break;
    }

    finished = checkState();
  }

  // 获取下一步可能的移动方向
  inline vector<DIRECTION> getNextMoves() {
    vector<DIRECTION> des;
    if (zero_pos.col > 0) des.push_back(DIRECTION::MOVE_RIGHT);
    if (zero_pos.col < cell_num - 1) des.push_back(DIRECTION::MOVE_LEFT);
    if (zero_pos.row > 0) des.push_back(DIRECTION::MOVE_DOWN);
    if (zero_pos.row < cell_num - 1) des.push_back(DIRECTION::MOVE_UP);

    return des;
  }

  // 随机移动
  inline void randomMove(int num) {
    vector<DIRECTION> des;
    for (int i = 0; i < num; i++) {
      auto des = getNextMoves();
      // 随机移动
      auto direct = des[rand() % des.size()];
      moveCell(direct);
    }
  }

  // 按一定的策略移动
  inline void searchMove() {
    
    auto des = getNextMoves();
  }

  inline bool checkState() {
    for (int i = 0; i < cell_num * cell_num - 1; i++) {
      if (cells[i / cell_num][i % cell_num] != i + 1) return false;
    }
    return true;
  }

 private:
  bool finished = false;
  int level = 0;  // 0,1,2
  int cell_num;
  Postion zero_pos;                       // 0 的位置
  constexpr static int MAX_LEN = 5;       // 最大 5x5
  constexpr static int CELL_WIDTH = 150;  // cell边长
  int cells[MAX_LEN][MAX_LEN];
};

int main() {
  NPuzzleGame game;
  game.run();
  return 0;
}
